package de.jmda.core.mproc.task;

import static java.lang.System.currentTimeMillis;
import static java.lang.System.lineSeparator;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.processing.Processor;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;

import org.apache.commons.collections.CollectionUtils;

import de.jmda.core.MarkerAnnotationType;
import de.jmda.core.util.JavaCompilerUtil;
import de.jmda.core.util.ramcomp.JavaSourceCodeObject;

/**
 * Provides {@link #run(AbstractTypeElementsTask...)} as utility method.
 *
 * @author roger@jmda.de
 */
public abstract class TaskRunnerTypeElements
{
	/**
	 * Each task defines a set of packages and a set of types to specify type
	 * elements that it wants to process in its {@link Task#execute()} method
	 * (see {@link AbstractTypeElementsTask}). This method defines and runs
	 * processors so that the task's {@link Task#execute()} method will be called
	 * and the specified type elements can be accessed via {@link
	 * AbstractTypeElementsTask#getTypeElements()}.
	 *
	 * @param tasks see above
	 * @throws IOException
	 */
	public static void run(AbstractTypeElementsTask... tasks) throws IOException
	{
		run(null, tasks);
	}

	public static void run(
			Iterable<String> options,
			AbstractTypeElementsTask... tasks) throws IOException
	{
		if (options == null) options = new ArrayList<>();
		if (tasks == null) return;
		if (tasks.length == 0) return;

		JavaCompiler compiler = JavaCompilerUtil.createJavaCompiler();

		Iterable<? extends JavaFileObject> compilationUnits =
				createJavaFileObjects(compiler, tasks);
		
		Iterable<? extends Processor> processors = createProcessors(tasks);
		
		JavaCompilerUtil.runProcessorsOnly(
				compiler, options, compilationUnits, processors);
	}

	private static Iterable<? extends Processor> createProcessors(
			AbstractTypeElementsTask... tasks)
  {
		List<Processor> result = new ArrayList<>();

		for (AbstractTypeElementsTask task : tasks)
		{
			result.add(new TypeElementsTaskProcessor(task));
		}

		return result;
  }

	/**
	 * @param javaCompiler 
	 * @param tasks
	 * @return list of java file objects as returned by {@link
	 *         #createJavaFileObjects(AbstractTypeElementsTask)} accumulated for
	 *         each element in <code>tasks</code>
	 */
	private static Iterable<? extends JavaFileObject> createJavaFileObjects(
			JavaCompiler javaCompiler, AbstractTypeElementsTask... tasks)
	{
		List<JavaFileObject> result = new ArrayList<>();

		for (AbstractTypeElementsTask task : tasks)
		{
			result.addAll(createJavaFileObjects(javaCompiler, task));
		}

		return result;
	}
	
	/**
	 * @param javaCompiler 
	 * @param task
	 * @return list of {@link MarkerAnnotationType} annotated java file objects
	 *         for <code>task</code> with one element for each package returned by
	 *         {@link #getPackagesWithTargetTypes(AbstractTypeElementsTask)}
	 */
	private static List<JavaFileObject> createJavaFileObjects(
			JavaCompiler javaCompiler, AbstractTypeElementsTask task)
  {
		List<JavaFileObject> result = new ArrayList<>();

		List<Package> packagesWithTargetTypes = getPackagesWithTargetTypes(task);
		
		long offset = currentTimeMillis();
		long counter = 0;

		String simpleName;

		for (Package package_ : packagesWithTargetTypes)
		{
			simpleName = "DynaCode" + offset + counter;

			result.add
			(
					new JavaSourceCodeObject
					(
							package_.getName() + "." + simpleName,
							"package " + package_.getName() + ";" + lineSeparator() +
							"@" + MarkerAnnotationType.class.getName() + lineSeparator() +
							"class " + simpleName + "{}"
					)
			);

			counter++;
		}

		CollectionUtils.addAll
		(
				result,
				javaCompiler.getStandardFileManager(null, null, null)
				            .getJavaFileObjectsFromFiles
				            (
				                task.getCustomRootFiles().calculateFileSet()
				            ).iterator()
		);

		return result;
  }

	/**
	 * @param task
	 * @return list of packages containing
	 *         <ul>
	 *           <li>all the packages from task ({@link
	 *               AbstractTypeElementsTask#getPackages()}) plus</li>
	 *           <li>all the packages of the types from task ({@link
	 *               AbstractTypeElementsTask#getTypes()})</li>
	 *         </ul>
	 */
	private static List<Package> getPackagesWithTargetTypes(AbstractTypeElementsTask task)
	{
		List<Package> result = new ArrayList<>(task.getPackages());
		
		for (Class<?> type : task.getTypes())
		{
			result.add(type.getPackage());
		}
		
		return result;
	}
}